#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>

#include <stdio.h>
#include <readline/readline.h>
#include <readline/history.h>

#include <string.h>
#include <signal.h>

/* FLAG CODES */
#define CHECK_ID                0x70 // Check if the requested user ID exists.
#define ADD_NEW                 0x71 // Adding more fingerprints to a current existing user ID.
#define CONTINUE                0x74 // There is more data to be sent.
#define AUTO_ID                 0x79 // Automatically assign user ID in enrollment.
#define CHECK_FINGER            0x84 // Check if the finger is already enrolled.
#define CHECK_FINGER_AUTO_ID    0x85 // Check if the finger is already enrolled. If not, assign user ID automatically.
#define ADD_DURESS              0x92 // Add duress fingerprints to an existing user ID.

/* ERROR CODES */
#define SUCCESS                 0x61 // Successfully completed.
#define SCAN_SUCCESS            0x62 // Fingerprint input has succeeded.
#define SCAN_FAIL               0x63 // Sensor or fingerprint input has failed.
#define NOT_FOUND               0x69 // There is no requested data found.
#define NOT_MATCH               0x6A // Fingerprint does not match.
#define TRY_AGAIN               0x6B // Fingerprint image is not good.
#define TIME_OUT                0x6C // Timeout for fingerprint input.
#define MEM_FULL                0x6D // Maximum template capacity exceeded.
#define EXIST_ID                0x6E // The requested user ID exists.
#define FINGER_LIMIT            0x72 // The number of fingerprints enrolled in same ID exceeds its limit (10).
#define UNSUPPORTED             0x75 // The command is not supported.
#define INVALID_ID              0x76 // The requested user ID is invalid or missing.
#define TIMEOUT_MATCH           0x7A // Timeout for matching in identification.
#define BUSY                    0x80 // Module is processing another command.
#define CANCELED                0x81 // The command is canceled.
#define DATA_ERROR              0x82 // The checksum of a data packet is incorrect.
#define DATA_OK                 0x83 // The checksum of a data packet is correct.
#define EXIST_FINGER            0x86 // The finger is already enrolled.
#define DURESS_FINGER           0x91 // A duress finger is detected.
#define LOCKED                  0xA1 // Module is locked.
#define REJECTED_ID             0x90 // Authentication mode of the user is AUTH_REJECT or the ID is in the blacklist.
#define ENTRANCE_LIMIT          0x94 // Authentication fails since the entrance limit is exceeded.

char* code_to_str(int code)
{
    switch(code) {
        case CHECK_ID               : return "Check if the requested user ID exists.";
        case ADD_NEW                : return "Adding more fingerprints to a current existing user ID.";
        case CONTINUE               : return "There is more data to be sent.";
        case AUTO_ID                : return "Automatically assign user ID in enrollment.";
        case CHECK_FINGER           : return "Check if the finger is already enrolled.";
        case CHECK_FINGER_AUTO_ID   : return "Check if the finger is already enrolled. If not, assign user ID automatically.";
        case ADD_DURESS             : return "Add duress fingerprints to an existing user ID.";
        case SUCCESS                : return "Successfully completed.";
        case SCAN_SUCCESS           : return "Fingerprint input has succeeded.";
        case SCAN_FAIL              : return "Sensor or fingerprint input has failed.";
        case NOT_FOUND              : return "There is no requested data found.";
        case NOT_MATCH              : return "Fingerprint does not match.";
        case TRY_AGAIN              : return "Fingerprint image is not good.";
        case TIME_OUT               : return "Timeout for fingerprint input.";
        case MEM_FULL               : return "Maximum template capacity exceeded.";
        case EXIST_ID               : return "The requested user ID exists.";
        case FINGER_LIMIT           : return "The number of fingerprints enrolled in same ID exceeds its limit (10).";
        case UNSUPPORTED            : return "The command is not supported.";
        case INVALID_ID             : return "The requested user ID is invalid or missing.";
        case TIMEOUT_MATCH          : return "Timeout for matching in identification.";
        case BUSY                   : return "Module is processing another command.";
        case CANCELED               : return "The command is canceled.";
        case DATA_ERROR             : return "The checksum of a data packet is incorrect.";
        case DATA_OK                : return "The checksum of a data packet is correct.";
        case EXIST_FINGER           : return "The finger is already enrolled.";
        case DURESS_FINGER          : return "A duress finger is detected.";
        case LOCKED                 : return "Module is locked.";
        case REJECTED_ID            : return "Authentication mode of the user is AUTH_REJECT or the ID is in the blacklist.";
        case ENTRANCE_LIMIT         : return "Authentication fails since the entrance limit is exceeded.";
        default: return "UNKNOWN CODE";
    }
}

void handler(int signal)
{
    printf("interrupted\n");
}

int open_tty()
{
    int fd, r;
    struct termios termio;

    memset (&termio, 0, sizeof (termio));

    if ((fd = open ("/dev/ttyUSB0", O_RDWR | O_NDELAY | O_NOCTTY)) == -1) {
        printf("Failed to open device\n");
        exit(-1);
    }

    fcntl(fd, F_SETFL, 0);
    r = tcgetattr(fd, &termio);

    /*
     *set the baud rate to 38400.
     */
    cfsetspeed(&termio, B115200);

    /*
     *set parity to none.
     */
    termio.c_cflag &= ~(PARENB | PARODD);

    /* *set databits to 8.
     */
    termio.c_cflag = ((termio.c_cflag & ~CSIZE) | CS8);

    /*
     *set flow control to none.
     */
    termio.c_cflag &= ~(CRTSCTS);
    termio.c_iflag &= ~(IXON | IXOFF | IXANY);
    termio.c_cflag |= (CLOCAL | CREAD);

    /*
     *apply our device config.
     */
    r = tcsetattr(fd, TCSANOW, &termio);

    if (r == -1) {
        printf("Failed to set termio options\n");
        exit(-1);
    }

    return fd;
}

typedef struct fpkt_s {
    uint8_t scode;
    uint8_t t_id[2];
    uint8_t cmd;
    uint8_t param[4];
    uint8_t size[4];
    uint8_t flag;
    uint8_t csum;
    uint8_t ecode;
} fpkt_t;

char calculate_csum(const fpkt_t pkt)
{
    char csum = 
        (pkt.scode + 
         pkt.cmd + 
         *((uint8_t*)&pkt.t_id + 0) + 
         *((uint8_t*)&pkt.t_id + 1) + 
         *((uint8_t*)&pkt.param + 0) + 
         *((uint8_t*)&pkt.param + 1) + 
         *((uint8_t*)&pkt.param + 2) + 
         *((uint8_t*)&pkt.param + 3) + 
         *((uint8_t*)&pkt.size + 0) + 
         *((uint8_t*)&pkt.size + 1) + 
         *((uint8_t*)&pkt.size + 2) + 
         *((uint8_t*)&pkt.size + 3) + 
         pkt.flag) % 0x100;

        return csum;
}

int main(int argc, char** argv)
{   
    int fd, rc;
    char *line;
    uint8_t *ptr;
    fpkt_t pkt, r_pkt = { 0x0 };

    fd = open_tty();
    pkt.ecode = 0x0A;

    struct sigaction new_action, old_action;

    /* Set up the structure to specify the new action. */
    new_action.sa_handler = handler;
    sigemptyset (&new_action.sa_mask);
    new_action.sa_flags = 0;

    sigaction (SIGINT, &new_action, NULL);

    while(1) {
        line = readline("[Fingerprint]$ ");
        if(strlen(line) == 0) {
            free(line);
            continue;
        }
        add_history(line);

        ptr = (uint8_t*)&pkt;
        char* token = strtok(line, ", ");
        while (token) {
            uint8_t tmp = strtol(token, NULL, 16);
            *ptr++ = tmp;
            token = strtok(NULL, ", ");
        }

        pkt.csum = calculate_csum(pkt);

        rc = write(fd, &pkt, sizeof(pkt));
        printf("[Fingerprint]$ ");
        int j;
        for(j = 0; j < sizeof(pkt); j++) {
            printf("0x%02X ", *(((uint8_t*)&pkt) + j));
        }

        printf("\n");

        do {
            rc = read(fd, &r_pkt, sizeof(r_pkt));
            printf("\nscode = 0x%x\n"
                    "tid = 0x%02x 0x%02x\n"
                    "cmd = 0x%x\n"
                    "param = 0x%x%x%x%x\n"
                    "size =  0x%x%x%x%x\n"
                    "flag = %s\n"
                    "csum = 0x%x\n"
                    "ecode = 0x%x\n",
                    r_pkt.scode, *((uint8_t*)&r_pkt.scode + 1), 
                                 *((uint8_t*)&r_pkt.scode + 2), r_pkt.cmd,
                    *((uint8_t*)&r_pkt.param + 0) , 
                    *((uint8_t*)&r_pkt.param + 1) , 
                    *((uint8_t*)&r_pkt.param + 2) , 
                    *((uint8_t*)&r_pkt.param + 3) , 
                    *((uint8_t*)&r_pkt.size + 0)  ,
                    *((uint8_t*)&r_pkt.size + 1)  ,
                    *((uint8_t*)&r_pkt.size + 2)  ,
                    *((uint8_t*)&r_pkt.size + 3)  ,
                    code_to_str(r_pkt.flag), r_pkt.csum, r_pkt.ecode);
        } while(rc > 0);

        free(line);
    }
    close(fd);

    return 0;
}
